net-ssh-gatewayでRubyプログラム内で多段SSH+ポートフォワードを行う
はじめに
こんにちは、最近は配達に忙しい佐々木です。
今回はVPC内のプライベートサブネットに存在するRedisへ接続してデータ操作を行うRubyスクリプトを作りたくてnet-ssh-gateway というgemを試してみたので、そのメモです。
やりたいこと
下記の図のように、VPC内のプライベートサブネットに存在するRedisにローカルのMacから接続します。 Redisにはプライベートサブネットからのみ接続できず、app インスタンスにはbastionインスタンスを経由しないと接続できません。
そこで次のような経路で接続を行います。
- appインスタンスへbastion経由の多段SSHへ接続します
- RedisへはSSHポートフォワードでMacのローカルポートを転送します
net-ssh-gateway
Rubyのプログラム内でSSHポートフォワードを行うにはnet-ssh-gateway が利用できます。 ポートフォワードでRedisへ接続する(多段SSHはなし)場合は下記のようになります。
require 'net/ssh/gateway' require 'redis' # SSH接続するインスタンスの情報 GW_USER = 'ec2-user' GW_HOST = 'app' SSH_OPT = { keys: ['~/.ssh/my_key.pem'], } # Redisの情報 REDIS_HOST = 'redis' REDIS_PORT = 6379 gateway = Net::SSH::Gateway.new( GW_HOST, GW_USER, SSH_OPT ) gateway.open(REDIS_HOST, REDIS_PORT) do |forwared_port| # フォワードされたローカルのポートへ接続する redis = Redis.new(host: '127.0.0.1', port: forwared_port, db: db) begin p redis.ping # PONG ensure redis.quit end end
多段SSHでポートフォワード
冒頭に書いた通り今回は多段SSHが必要です。多段SSHを行うにはNet::SSH::Proxy::Commandを使用します。
下記のようにSSH接続のオプションのキーproxy
にNet::SSH::Proxy::Command
のインスタンスを指定します。
Net::SSH::Proxy::Command.new
の引数は~/.ssh/config
などに記述するProxyCommand
と同じものでオッケーです。
require 'net/ssh/gateway' require 'redis' require 'net/ssh/proxy/command' # SSH接続するインスタンスの情報 GW_USER = 'ec2-user' GW_HOST = 'app' SSH_OPT = { keys: ['~/.ssh/my_key.pem'], proxy: Net::SSH::Proxy::Command.new('ssh -i ~/.ssh/my_key.pem -W %h:%p ec2-user@bastion') } # Redisの情報 REDIS_HOST = 'redis' REDIS_PORT = 6379 gateway = Net::SSH::Gateway.new( GW_HOST, GW_USER, SSH_OPT ) gateway.open(REDIS_HOST, REDIS_PORT) do |forwared_port| redis = Redis.new(host: '127.0.0.1', port: forwared_port, db: db) begin p redis.ping # PONG ensure redis.quit end end
注意点
リポジトリのREADMEに書かれている通り、net-ssh-gatewayはアクティブな開発が行われていません。
Please note: this project is in maintenance mode. It is not under active development but pull requests are very much welcome. Just be sure to include tests!
プロダクションコードで利用する際にはその点に留意が必要です。
まとめ
net-ssh-gatewayを使ってRubyでSSHポートフォワードと多段SSHを経由したRedis接続が行えました。 Ruby内で必要な前準備を完結できたので、ターミナルでポートフォワードを行うよりも手軽に実行できるスクリプトが作成できました。